﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Validity.Contexts;

namespace Validity
{
  public interface IValidator<T>
  {
    IEnumerable<ValidationResult> Validate(T value);
  }

  public abstract class ValidatorBase<T>
    : IValidator<T>
  {
    private readonly List<Func<T, ValidationResult>> _validators = new List<Func<T, ValidationResult>>();

    protected ValidatorBase() { }

    protected IValidationContext<T, TResult> DefineRulesFor<TResult>(Expression<Func<T, TResult>> expression)
    {
      return DefineRulesFor<TResult>(expression, null);
    }

    protected IValidationContext<T, TResult> DefineRulesFor<TResult>(Expression<Func<T, TResult>> expression, String description)
    {
      return DefineRulesFor<TResult>(expression, description, null);
    }

    protected IValidationContext<T, TResult> DefineRulesFor<TResult>(Expression<Func<T, TResult>> expression, String description, String propertyName)
    {
      Verify.NotNull(expression);

      String memberName = propertyName ?? (expression.Body.NodeType == ExpressionType.MemberAccess ? ((MemberExpression)expression.Body).Member.Name : null);
      String massagedDescription = description ?? memberName ?? expression.Type.Name;
      Func<T, TResult> valueRetriever = expression.Compile();

      ValidationContext<T, TResult> context = new ValidationContext<T, TResult>();

      _validators.Add((T value) => new ValidationResult(context.GetRuleViolationsFor(value, valueRetriever.Invoke(value)), massagedDescription, memberName));

      return context;
    }
    
    protected IValidationContext<T, ComparableNullable<TResult>> DefineRulesFor<TResult>(Expression<Func<T, Nullable<TResult>>> expression)
      where TResult : struct, IComparable
    {
      return DefineRulesFor<TResult>(expression, null);
    }

    protected IValidationContext<T, ComparableNullable<TResult>> DefineRulesFor<TResult>(Expression<Func<T, Nullable<TResult>>> expression, String description)
      where TResult : struct, IComparable
    {
      return DefineRulesFor<TResult>(expression, description, null);
    }

    protected IValidationContext<T, ComparableNullable<TResult>> DefineRulesFor<TResult>(Expression<Func<T, Nullable<TResult>>> expression, String description, String propertyName)
      where TResult : struct, IComparable
    {
      Verify.NotNull(expression);

      String memberName = propertyName ?? (expression.Body.NodeType == ExpressionType.MemberAccess ? ((MemberExpression)expression.Body).Member.Name : null);
      String massagedDescription = description ?? memberName ?? expression.Type.Name;
      Func<T, Nullable<TResult>> valueRetriever = expression.Compile();

      ValidationContext<T, ComparableNullable<TResult>> context = new ValidationContext<T, ComparableNullable<TResult>>();

      _validators.Add((T value) => new ValidationResult(context.GetRuleViolationsFor(value, valueRetriever.Invoke(value)), massagedDescription, memberName));

      return context;
    }

    public IEnumerable<ValidationResult> Validate(T value)
    {
      Verify.False(Equals(value, null));

      foreach (var validator in _validators)
        yield return validator.Invoke(value);
    }
  }
}
